package edu.snake.implement.model.arena;


import edu.snake.core.model.arena.Arena;
import edu.snake.core.model.arena.Position;
import edu.snake.core.model.character.Character;
import edu.snake.implement.model.CharacterName;
import edu.snake.implement.model.arena.iterator.SimpleArenaIterator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static edu.snake.implement.ConfigConstant.getInstanceFactory;
import static edu.snake.implement.model.arena.PositionFactory.getNewPosition;

public class ArenaImpl implements Arena {
    private final Lock arenaLock = new ReentrantLock();
    private final int width;
    private final int height;
    private final Character[][] grid;

    public ArenaImpl(int width, int height) {
        this.width = width;
        this.height = height;
        grid = new Character[width][height];

    }

    public Lock getLock() {
        return arenaLock;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    @Override
    public void set(Position pos, Character character) {
        checkBoundsException(pos);
        grid[pos.getWidth()][pos.getHeight()] = character;
    }

    @Override
    public Character move(Position position, Character character) {
        checkBoundsException(position);
        if (character != null && get(character) != null) {
            remove(get(character));
            set(position, character);
        }
        return null;
    }

    @Override
    public Character move(Direction direction, Character character) {
        if (character == null || direction == null) {
            return null;
        }
        Position position = get(character);
        if (position == null) {
            throw new RuntimeException("Arena:  " + character);
        }
        Position newPosition = getNewPosition(position, direction);
        if (checkArenaBorder(newPosition)) {
            return getInstanceFactory().createNewCharacter(CharacterName.BORDER);
        }

        remove(position);
        Character result = get(newPosition);
        set(newPosition, character);
        return result;
    }

    @Override
    public Position get(Character character) {
        Iterator<Position> positionIterator = this.iterator();
        while (positionIterator.hasNext()) {
            Position position = positionIterator.next();
            int width = position.getWidth();
            int height = position.getHeight();
            Character character1 = grid[width][height];
            if (character1 != null && character1.equals(character)) {
                return position;
            }
        }
        return null;
    }

    @Override
    public Character get(Position position) {
        checkBoundsException(position);
        return grid[position.getWidth()][position.getHeight()];
    }

    private void checkBoundsException(Position position) {
        int width = position.getWidth();
        int height = position.getHeight();
        if (width >= grid.length || width < 0) {
            throw new OutOfAxisXGridException("X not exist " + width);
        }

        if (height >= grid[0].length || height < 0) {
            throw new OutOfAxisYGridException("Y not exist" + height);
        }
    }

    @Override
    public Character remove(Position position) {
        checkBoundsException(position);
        Character result = grid[position.getWidth()][position.getHeight()];
        grid[position.getWidth()][position.getHeight()] = null;
        return result;
    }

    @Override
    public Position remove(Character character) {
        Position charPosition = get(character);
        grid[charPosition.getWidth()][charPosition.getHeight()] = null;
        return charPosition;
    }

    @Override
    public boolean checkFreeSpace() {
        for (Character[] aGrid : grid) {
            for (int j = 0; j < grid[0].length; j++) {
                if (aGrid[j] == null) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public List<Position> freeSpaceList() {
        List<Position> freePositionList = new ArrayList<Position>();
        Iterator<Position> positionIterator = this.iterator();
        while (positionIterator.hasNext()) {
            Position position = positionIterator.next();
            if (this.get(position) == null) {
                freePositionList.add(position);
            }
        }
        return freePositionList;
    }

    public Position calculateNeighboringFreePosition(Position position) {
        Position result = getNewPosition(position, Direction.LEFT);
        if (!checkArenaBorder(result) && this.get(result) == null) {
            return result;
        }
        result = getNewPosition(position, Direction.UP);
        if (!checkArenaBorder(result) && this.get(result) == null) {
            return result;
        }
        result = getNewPosition(position, Direction.RIGHT);
        if (!checkArenaBorder(result) && this.get(result) == null) {
            return result;
        }
        result = getNewPosition(position, Direction.DOWN);
        if (!checkArenaBorder(result) && this.get(result) == null) {
            return result;
        }
        throw new RuntimeException("Can not grow a snake, all position are closed near the tail");
    }

    /**
     * @return <i>true</i> if position out of border of arena.
     */
    @Override
    public boolean checkArenaBorder(Position position) {
        int width = position.getWidth();
        int height = position.getHeight();
        return width >= grid.length || width < 0 || height >= grid[0].length || height < 0;

    }

    @Override
    public Iterator<Position> iterator() {
        return new SimpleArenaIterator(this);
    }

    @Override
    public String toString() {
        return "ArenaImpl{" +
                "height=" + height +
                ", width=" + width +
                '}';
    }
}
